<?php

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

namespace App\HttpController\adminapi;

use App\Constant\ApiErrorCode;
use App\Model\Admin\SystemAdminModel;
use App\Services\System\Admin\SystemRoleServices;
use EasySwoole\EasySwoole\Config;
use EasySwoole\EasySwoole\ServerManager;
use EasySwoole\FastCache\Cache;
use EasySwoole\Http\AbstractInterface\Controller;
use EasySwoole\Http\Message\Status;
use EasySwoole\Jwt\Jwt;
use EasySwoole\Policy\Policy;
use EasySwoole\Policy\PolicyNode;
use EasySwoole\Validate\Validate;
use stdClass;

/**
 * Description of BaseController
 *
 * @author zike
 */
abstract class AuthController extends Controller {

    private $basicAction = [
	'/adminapi/login/login', '/adminapi/login/info', '/adminapi/login/captcha', '/adminapi/login/test',
    ];
    protected $token;
    protected $id;
    protected $type;
    protected $adminInfo;

    public function index() {
	$this->actionNotFound('index');
    }

    public function onRequest(?string $action): ?bool {
	if (!parent::onRequest($action)) {
	    return false;
	}

	$rawData = $this->request()->getBody()->__toString();
	if ($rawData) {
	    $postData = json_decode($rawData, true);
	    $this->request()->withParsedBody($postData);
	}

	$path = $this->request()->getUri()->getPath();
	if (!in_array($path, $this->basicAction)) {// basic列表里的不需要验证
	    // 必须有token
	    if (empty($this->request()->getHeader('authori-zation')[0])) {
		$this->writeJson(ApiErrorCode::ERR_LOGIN_BAD[0], new stdClass(), "token不可为空");
		return false;
	    }

	    //jwt
	    $jwtConfig = Config::getInstance()->getConf('JWT');
	    $bearer = trim(ltrim($this->request()->getHeader('authori-zation')[0], 'Bearer'));
	    $jwtObject = Jwt::getInstance()->setSecretKey($jwtConfig['key'])->decode($bearer);
	    $status = $jwtObject->getStatus();
	    // 如果encode设置了秘钥,decode 的时候要指定
	    switch ($status) {
		case 1:
		    $this->token = $jwtObject->getData();
		    break;
		case -1:
		    $this->writeJson(ApiErrorCode::ERR_LOGIN_BAD[0], new stdClass(), "token无效");
		    return false;
		    break;
		case -2:
		    $this->writeJson(ApiErrorCode::ERR_LOGIN_INVALID[0], new stdClass(), "token过期");
		    return false;
		    break;
	    }
	    if (!is_array($this->token) || empty($this->token)) {
		$this->writeJson(ApiErrorCode::ERR_LOGIN_STATUS[0], new stdClass(), "token解析失败:" . $this->token);
		return false;
	    }
	    $tokenCache = Cache::getInstance()->get(md5($bearer));
	    if ($tokenCache === null || $tokenCache != $this->token) {
		$this->writeJson(ApiErrorCode::ERR_LOGIN_STATUS[0], new stdClass(), "token过期1");
		return false;
	    }
	    //获取管理员信息
	    $this->adminId = $this->token[0];
	    $this->type = $this->token[1];
	    $this->adminInfo = SystemAdminModel::create()->get($this->adminId);
	    if (!$this->adminInfo || !$this->adminInfo->id) {
		Cache::getInstance()->unset(md5($bearer));
		$this->writeJson(ApiErrorCode::ERR_LOGIN_STATUS[0], new stdClass(), "token过期2");
		return false;
	    }

	    // 权限策略判断	    
	    $method = $this->request()->getMethod();
	    $route = substr($this->request()->getAttribute("_route"), 9); //@todo 兼容
	    if (0 && !$this->vifPolicy($this->adminInfo->roles, "{$method}{$route}")) {//todo 暂时关闭测试方便
		$this->writeJson(Status::CODE_BAD_REQUEST, new stdClass(), "无权限访问该接口");
		return false;
	    }
	}

	// 各个action的参数校验
	$v = $this->getValidateRule($action);
	if ($v && !$this->validate($v)) {
	    $this->writeJson(Status::CODE_BAD_REQUEST, ['errorCode' => 1, 'data' => []], $v->getError()->__toString());
	    return false;
	}

	return true;
    }

    /**
     * 验证权限策略
     * @param $u_id
     * @param string $path
     * @return bool
     * @throws
     */
    private function vifPolicy($rules, string $path) {
	if (empty($rules))
	    return false;

	// 从缓存拿 没有就从数据库读取 初始化
	$key = 'policy_' . implode('_', $rules);
	$policy = Cache::getInstance()->get($key);
	if ($policy === null) {
	    $policy = new Policy();
	    $userAuth = SystemRoleServices::getInstance()->getRolesByAuth($rules, 2); // 用户权限
	    foreach ($userAuth as $key => $value) {
		$pathTmp = str_replace("/product/product/", "/product/", "{$value['methods']}/{$value['api_url']}"); //@todo 兼容
		$policy->addPath($pathTmp, PolicyNode::EFFECT_ALLOW);
	    }
	    Cache::getInstance()->set($key, serialize($policy), 10 * 60);
	} else {
	    $policy = unserialize($policy);
	}

	if ($policy->check($path) === 'allow') {
	    return true;
	}
	return false;
    }

    abstract protected function getValidateRule(?string $action): ?Validate;

    protected function validate(Validate $validate) {
	return $validate->validate($this->request()->getRequestParam());
    }

//    function onException(\Throwable $throwable): void
//    {
//        var_dump($throwable->getMessage());
//    }

    /**
     * 获取用户的真实IP
     * @param string $headerName 代理服务器传递的标头名称
     * @return string
     */
    protected function clientRealIP($headerName = 'x-real-ip') {
	$server = ServerManager::getInstance()->getSwooleServer();
	$client = $server->getClientInfo($this->request()->getSwooleRequest()->fd);
	$clientAddress = $client['remote_ip'];
	$xri = $this->request()->getHeader($headerName);
	$xff = $this->request()->getHeader('x-forwarded-for');
	if ($clientAddress === '127.0.0.1') {
	    if (!empty($xri)) {  // 如果有 xri 则判定为前端有 NGINX 等代理
		$clientAddress = $xri[0];
	    } elseif (!empty($xff)) {  // 如果不存在 xri 则继续判断 xff
		$list = explode(',', $xff[0]);
		if (isset($list[0]))
		    $clientAddress = $list[0];
	    }
	}
	return $clientAddress;
    }

    public function writeJson($statusCode = 200, $result = NULL, $msg = NULL) {
	if (!$this->response()->isEndResponse()) {
	    $data = Array(
		"status" => $statusCode,
		"msg" => $msg,
		"data" => $result
	    );
	    $this->response()->write(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
	    $this->response()->withHeader('Content-type', 'application/json;charset=utf-8');
	    $this->response()->withStatus(200);
	    return true;
	} else {
	    return false;
	}
    }

}
